/*!
 * headroom.js v0.7.0 - Give your page some headroom. Hide your header until you need it
 * Copyright (c) 2014 Nick Williams - http://wicky.nillia.ms/headroom.js
 * License: MIT
 */

(function(window, document) {

    'use strict';

    /* exported features */

    var features = {
        bind : !!(function(){}.bind),
        classList : 'classList' in document.documentElement,
        rAF : !!(window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame)
    };
    window.requestAnimationFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame;

    /**
     * Handles debouncing of events via requestAnimationFrame
     * @see http://www.html5rocks.com/en/tutorials/speed/animations/
     * @param {Function} callback The callback to handle whichever event
     */
    function Debouncer (callback) {
        this.callback = callback;
        this.ticking = false;
    }
    Debouncer.prototype = {
        constructor : Debouncer,

        /**
         * dispatches the event to the supplied callback
         * @private
         */
        update : function() {
            this.callback && this.callback();
            this.ticking = false;
        },

        /**
         * ensures events don't get stacked
         * @private
         */
        requestTick : function() {
            if(!this.ticking) {
                requestAnimationFrame(this.rafCallback || (this.rafCallback = this.update.bind(this)));
                this.ticking = true;
            }
        },

        /**
         * Attach this as the event listeners
         */
        handleEvent : function() {
            this.requestTick();
        }
    };
    /**
     * Check if object is part of the DOM
     * @constructor
     * @param {Object} obj element to check
     */
    function isDOMElement(obj) {
        return obj && typeof window !== 'undefined' && (obj === window || obj.nodeType);
    }

    /**
     * Helper function for extending objects
     */
    function extend (object /*, objectN ... */) {
        if(arguments.length <= 0) {
            throw new Error('Missing arguments in extend function');
        }

        var result = object || {},
            key,
            i;

        for (i = 1; i < arguments.length; i++) {
            var replacement = arguments[i] || {};

            for (key in replacement) {
                // Recurse into object except if the object is a DOM element
                if(typeof result[key] === 'object' && ! isDOMElement(result[key])) {
                    result[key] = extend(result[key], replacement[key]);
                }
                else {
                    result[key] = result[key] || replacement[key];
                }
            }
        }

        return result;
    }

    /**
     * Helper function for normalizing tolerance option to object format
     */
    function normalizeTolerance (t) {
        return t === Object(t) ? t : { down : t, up : t };
    }

    /**
     * UI enhancement for fixed headers.
     * Hides header when scrolling down
     * Shows header when scrolling up
     * @constructor
     * @param {DOMElement} elem the header element
     * @param {Object} options options for the widget
     */
    function Headroom (elem, options) {
        options = extend(options, Headroom.options);

        this.lastKnownScrollY = 0;
        this.elem             = elem;
        this.debouncer        = new Debouncer(this.update.bind(this));
        this.tolerance        = normalizeTolerance(options.tolerance);
        this.classes          = options.classes;
        this.offset           = options.offset;
        this.scroller         = options.scroller;
        this.initialised      = false;
        this.onPin            = options.onPin;
        this.onUnpin          = options.onUnpin;
        this.onTop            = options.onTop;
        this.onNotTop         = options.onNotTop;
    }
    Headroom.prototype = {
        constructor : Headroom,

        /**
         * Initialises the widget
         */
        init : function() {
            if(!Headroom.cutsTheMustard) {
                return;
            }

            this.elem.classList.add(this.classes.initial);

            // defer event registration to handle browser
            // potentially restoring previous scroll position
            setTimeout(this.attachEvent.bind(this), 100);

            return this;
        },

        /**
         * Unattaches events and removes any classes that were added
         */
        destroy : function() {
            var classes = this.classes;

            this.initialised = false;
            this.elem.classList.remove(classes.unpinned, classes.pinned, classes.top, classes.initial);
            this.scroller.removeEventListener('scroll', this.debouncer, false);
        },

        /**
         * Attaches the scroll event
         * @private
         */
        attachEvent : function() {
            if(!this.initialised){
                this.lastKnownScrollY = this.getScrollY();
                this.initialised = true;
                this.scroller.addEventListener('scroll', this.debouncer, false);

                this.debouncer.handleEvent();
            }
        },

        /**
         * Unpins the header if it's currently pinned
         */
        unpin : function() {
            var classList = this.elem.classList,
                classes = this.classes;

            if(classList.contains(classes.pinned) || !classList.contains(classes.unpinned)) {
                classList.add(classes.unpinned);
                classList.remove(classes.pinned);
                this.onUnpin && this.onUnpin.call(this);
            }
        },

        /**
         * Pins the header if it's currently unpinned
         */
        pin : function() {
            var classList = this.elem.classList,
                classes = this.classes;

            if(classList.contains(classes.unpinned)) {
                classList.remove(classes.unpinned);
                classList.add(classes.pinned);
                this.onPin && this.onPin.call(this);
            }
        },

        /**
         * Handles the top states
         */
        top : function() {
            var classList = this.elem.classList,
                classes = this.classes;

            if(!classList.contains(classes.top)) {
                classList.add(classes.top);
                classList.remove(classes.notTop);
                this.onTop && this.onTop.call(this);
            }
        },

        /**
         * Handles the not top state
         */
        notTop : function() {
            var classList = this.elem.classList,
                classes = this.classes;

            if(!classList.contains(classes.notTop)) {
                classList.add(classes.notTop);
                classList.remove(classes.top);
                this.onNotTop && this.onNotTop.call(this);
            }
        },

        /**
         * Gets the Y scroll position
         * @see https://developer.mozilla.org/en-US/docs/Web/API/Window.scrollY
         * @return {Number} pixels the page has scrolled along the Y-axis
         */
        getScrollY : function() {
            return (this.scroller.pageYOffset !== undefined)
                ? this.scroller.pageYOffset
                : (this.scroller.scrollTop !== undefined)
                ? this.scroller.scrollTop
                : (document.documentElement || document.body.parentNode || document.body).scrollTop;
        },

        /**
         * Gets the height of the viewport
         * @see http://andylangton.co.uk/blog/development/get-viewport-size-width-and-height-javascript
         * @return {int} the height of the viewport in pixels
         */
        getViewportHeight : function () {
            return window.innerHeight
            || document.documentElement.clientHeight
            || document.body.clientHeight;
        },

        /**
         * Gets the height of the document
         * @see http://james.padolsey.com/javascript/get-document-height-cross-browser/
         * @return {int} the height of the document in pixels
         */
        getDocumentHeight : function () {
            var body = document.body,
                documentElement = document.documentElement;

            return Math.max(
                body.scrollHeight, documentElement.scrollHeight,
                body.offsetHeight, documentElement.offsetHeight,
                body.clientHeight, documentElement.clientHeight
            );
        },

        /**
         * Gets the height of the DOM element
         * @param  {Object}  elm the element to calculate the height of which
         * @return {int}     the height of the element in pixels
         */
        getElementHeight : function (elm) {
            return Math.max(
                elm.scrollHeight,
                elm.offsetHeight,
                elm.clientHeight
            );
        },

        /**
         * Gets the height of the scroller element
         * @return {int} the height of the scroller element in pixels
         */
        getScrollerHeight : function () {
            return (this.scroller === window || this.scroller === document.body)
                ? this.getDocumentHeight()
                : this.getElementHeight(this.scroller);
        },

        /**
         * determines if the scroll position is outside of document boundaries
         * @param  {int}  currentScrollY the current y scroll position
         * @return {bool} true if out of bounds, false otherwise
         */
        isOutOfBounds : function (currentScrollY) {
            var pastTop  = currentScrollY < 0,
                pastBottom = currentScrollY + this.getViewportHeight() > this.getScrollerHeight();

            return pastTop || pastBottom;
        },

        /**
         * determines if the tolerance has been exceeded
         * @param  {int} currentScrollY the current scroll y position
         * @return {bool} true if tolerance exceeded, false otherwise
         */
        toleranceExceeded : function (currentScrollY, direction) {
            return Math.abs(currentScrollY-this.lastKnownScrollY) >= this.tolerance[direction];
        },

        /**
         * determine if it is appropriate to unpin
         * @param  {int} currentScrollY the current y scroll position
         * @param  {bool} toleranceExceeded has the tolerance been exceeded?
         * @return {bool} true if should unpin, false otherwise
         */
        shouldUnpin : function (currentScrollY, toleranceExceeded) {
            var scrollingDown = currentScrollY > this.lastKnownScrollY,
                pastOffset = currentScrollY >= this.offset;

            return scrollingDown && pastOffset && toleranceExceeded;
        },

        /**
         * determine if it is appropriate to pin
         * @param  {int} currentScrollY the current y scroll position
         * @param  {bool} toleranceExceeded has the tolerance been exceeded?
         * @return {bool} true if should pin, false otherwise
         */
        shouldPin : function (currentScrollY, toleranceExceeded) {
            var scrollingUp  = currentScrollY < this.lastKnownScrollY,
                pastOffset = currentScrollY <= this.offset;

            return (scrollingUp && toleranceExceeded) || pastOffset;
        },

        /**
         * Handles updating the state of the widget
         */
        update : function() {
            var currentScrollY  = this.getScrollY(),
                scrollDirection = currentScrollY > this.lastKnownScrollY ? 'down' : 'up',
                toleranceExceeded = this.toleranceExceeded(currentScrollY, scrollDirection);

            if(this.isOutOfBounds(currentScrollY)) { // Ignore bouncy scrolling in OSX
                return;
            }

            if (currentScrollY <= this.offset ) {
                this.top();
            } else {
                this.notTop();
            }

            if(this.shouldUnpin(currentScrollY, toleranceExceeded)) {
                this.unpin();
            }
            else if(this.shouldPin(currentScrollY, toleranceExceeded)) {
                this.pin();
            }

            this.lastKnownScrollY = currentScrollY;
        }
    };
    /**
     * Default options
     * @type {Object}
     */
    Headroom.options = {
        tolerance : {
            up : 0,
            down : 0
        },
        offset : 0,
        scroller: window,
        classes : {
            pinned : 'headroom--pinned',
            unpinned : 'headroom--unpinned',
            top : 'headroom--top',
            notTop : 'headroom--not-top',
            initial : 'headroom'
        }
    };
    Headroom.cutsTheMustard = typeof features !== 'undefined' && features.rAF && features.bind && features.classList;

    window.Headroom = Headroom;


}(window, document));